﻿using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;

namespace SampleCode.VirtualFS.ViewModel
{
    /// <summary>
    /// The root disk object View Model
    /// </summary>
    public class FsDiskViewModel : FsViewModelBase
    {
        /// <summary>
        /// A reference to Dispatcher. Used for thread-safe ObservableCollections
        /// </summary>
        private readonly Dispatcher _dispatcher;

        /// <summary>
        /// The disk object
        /// </summary>
        private FsDisk _disk;

        /// <summary>
        /// Currently opened physical file name
        /// </summary>
        private string _fileName;

        /// <summary>
        /// A flag for cancelling async operations
        /// </summary>
        private bool _cancelAsyncOperation;

        /// <summary>
        /// A ctor
        /// </summary>
        /// <param name="dispatcher"></param>
        public FsDiskViewModel(Dispatcher dispatcher)
        {
            _dispatcher = dispatcher;
        }

        private readonly ObservableCollection<FsFolderViewModel> _folders = new ObservableCollection<FsFolderViewModel>();
        /// <summary>
        /// Collection of folder View Models. Consists of only root folder, which cannot be changed or removed.
        /// So doesn't need to be thread-safe.
        /// </summary>
        public ObservableCollection<FsFolderViewModel> Folders
        {
            get { return _folders; }
        }

        private FsRelayCommand _createDiskCommand;
        /// <summary>
        /// Create new VirtualDisk command
        /// </summary>
        public ICommand CreateDiskCommand
        {
            get
            {
                return _createDiskCommand ?? (_createDiskCommand = new FsRelayCommand
                (
                    fileName => InitDisk( (string)fileName, true) )
                );
            }
        }

        private FsRelayCommand _openDiskCommand;
        /// <summary>
        /// Open an existing VirtualDisk command
        /// </summary>
        public ICommand OpenDiskCommand
        {
            get
            {
                return _openDiskCommand ?? (_openDiskCommand = new FsRelayCommand
                (
                    fileName => InitDisk( (string)fileName, false) )
                );
            }
        }

        private FsRelayCommand _cancelCommand;
        /// <summary>
        /// Cancel async operation commmand
        /// </summary>
        public ICommand CancelCommand
        {
            get
            {
                return _cancelCommand ?? (_cancelCommand = new FsRelayCommand
                (
                    _ => { _cancelAsyncOperation = true; }
                ));
            }
        }

        private FsRelayCommand _threadTestCommand;
        /// <summary>
        /// Start multi-thread test command
        /// </summary>
        public ICommand ThreadTestCommand
        {
            get
            {
                return _threadTestCommand ?? (_threadTestCommand = new FsRelayCommand
                (
                    _ => StartTest()
                ));
            }
        }

        private string _status = "Ready";
        /// <summary>
        /// Status bar text
        /// </summary>
        public string Status
        {
            get { return _status; }
            private set
            {
                bool notifyNeeded = _status != value;

                _status = value;

                if(notifyNeeded)
                    OnPropertyChanged("Status");
            }
        }

        private bool _asyncOperationInProgress;
        /// <summary>
        /// Are async operation buttons enabled at the moment?
        /// Only one async operation is supported at a time.
        /// </summary>
        public bool AsyncOperationsEnabled
        {
            get { return !_asyncOperationInProgress; }
            private set
            {
                _asyncOperationInProgress = !value;
                OnPropertyChanged("CancelButtonVisibility");
                OnPropertyChanged("AsyncOperationsEnabled");
            }
        }

        /// <summary>
        /// Is Cancel button visible at the moment?
        /// </summary>
        public Visibility CancelButtonVisibility
        {
            get
            {
                // a special case - when multi-thread test is started
                if (_folders.Count <= 0)
                    return Visibility.Collapsed;

                return _asyncOperationInProgress ? Visibility.Visible : Visibility.Collapsed;
            }
        }

        /// <summary>
        /// An event is fired, when a disk is successfully created or opened
        /// </summary>
        public event Action DiskCreated;

        /// <summary>
        /// Load root folder View Model
        /// </summary>
        private void CreateFolders()
        {
            _folders.Add(new FsFolderViewModel(_dispatcher, _disk, _disk.RootFolder));
        }

        /// <summary>
        /// Unload root folder View Model
        /// </summary>
        private void DisposeFolders()
        {
            // Disposing folder ViewModels
            foreach (var vm in _folders)
                vm.Dispose();
            _folders.Clear();
        }

        /// <summary>
        /// Create new or open an existing VirtualDisk
        /// </summary>
        /// <param name="fileName">Physical file name</param>
        /// <param name="bRemoveOld">Should be true when creating a new disk - for the existing file to be cleared</param>
        protected void InitDisk(string fileName, bool bRemoveOld)
        {
            OnDispose();

            if ( (bRemoveOld) && (File.Exists(fileName)) )
                File.Delete(fileName);

            _fileName = fileName;
            _disk = new FsDisk(_fileName);

            _disk.AsyncOperationProgress += AsyncOperationProgress;
            _disk.AsyncOperationCompleted += AsyncOperationCompleted;

            CreateFolders();

            Status = _fileName;

            if (DiskCreated != null)
                DiskCreated();
        }

        /// <summary>
        /// Start multi-thread test
        /// </summary>
        private void StartTest()
        {
            if (_disk.GetSubFolders(@"\").Count() <= 0)
                throw new InvalidOperationException("There're no folders on disk. Create some folders first!");

            // As multi-thread test will start many threads, we need to detach from _disk's change events,
            // because our FsThreadSafeObservableCollection supports only one worker thread
            DisposeFolders();

            var threadTest = new FsMultiThreadTest(_disk);
            threadTest.TestCompleted += AsyncOperationCompleted;

            threadTest.StartTest();

            // turning off other async operations till the current one ends
            AsyncOperationsEnabled = false;

            Status = "Multi-thread test is in progress - please, wait...";
        }

        /// <summary>
        /// Async operation progress event handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        /// <returns>true, if the operation was cancelled</returns>
        private bool AsyncOperationProgress(object sender, FsProgressChangedEventArgs e)
        {
            // turning off other async operations till the current one ends
            AsyncOperationsEnabled = false;

            Status = e.ProgressDesc;
            return _cancelAsyncOperation;
        }

        /// <summary>
        /// Async operation completed event handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void AsyncOperationCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {
            _cancelAsyncOperation = false;

            if (e.Error != null)
                Status = "Async operation error: " + e.Error.Message;
            else if (e.Cancelled)
                Status = "Async operation cancelled!";
            else
                Status = "Async operation completed successfully!";

            AsyncOperationsEnabled = true;

            // if we were temporary detached from disk - attaching back
            if (_folders.Count <= 0)
                CreateFolders();
        }

        protected override void OnDispose()
        {
            DisposeFolders();

            if(_disk != null)
                _disk.Dispose();
            _disk = null;
        }
    }
}
